/**
 * Auteur : Dominique GUERIN
 * dominiquephDOTguerinATgmailDOT..
 * dominiqueDOTguerinATinseeDOTfr(ance)  
 * Remerciements: � Keith Brown pour ses deux livres sur la s�curit� sous Windows et ses diff�rents articles
 *                 et � Michel Barnett de Microsoft pour ses deux articles 
 *                ".NET Remoting Authentication and Authorization Sample"  et le code qui les accompagne
 *  Le code est utilisable, modifiable et redistribuable � volont� sous la condition de ne pas supprimer ces 7 lignes.
 */ 
 
 package fr.doume.cli.authenticator;


import java.io.IOException;
import java.security.Principal;
import java.util.Date;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;


//tomcat 5.5
//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;

//tomcat 6
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import org.apache.catalina.authenticator.AuthenticatorBase;

import fr.doume.cli.sspi.SSPException;
import fr.doume.cli.sspi.SSPAuthentication;
import fr.doume.cli.sspi.SSPAuthShadow;
import fr.doume.cli.sspi.TranslationRolesIntoSids;

import fr.doume.cli.realm.WindowsRealm;
import fr.doume.cli.authenticator.Parameters;
import fr.doume.cli.authenticator.AuthenticationContext;
import fr.doume.cli.authenticator.HashMapWithTimeOut;

import org.apache.catalina.authenticator.Constants;

import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext;
import javax.servlet.ServletContext;

import java.util.ArrayList;

import org.apache.catalina.realm.GenericPrincipal;

import fr.doume.cli.base64.Base64;
import fr.doume.cli.base64.FormatBase64Exception;

import org.apache.catalina.Realm;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.ServletException;




/**
 * The class <c><b>SSPAuthenticator </b></c> implements the authentication defined 
 * in the RFC 4559: "SPNEGO-based Kerberos and NTLM HTTP Authentication in 
 * Microsoft Windows".<p> This authentication is only for intranet<br/>
 * SSPI=Security Service Provider Interface<br>
 * GSSAPI=Generic Security Services Application Programming Interface <p>
 * The SPNEGO authentication uses GSSAPI or SSPI.
 * "The GSSAPI, by itself, does not provide any security. 
 * Instead, security service vendors provide GSSAPI implementations usually in the form of libraries 
 * installed with their security software. SSPI is  the version of Microsoft of GSSAPI.
 * The definitive feature of GSSAPI applications is the exchange of opaque messages (tokens) 
 * that hide the implementation detail from the higher level application. 
 * The client and server sides of the application are written to convey the 
 * tokens given to them by their respective GSSAPI implementations." 
 * "GSSAPI tokens can usually be sent over an insecure network as the mechanisms provide inherent message security. 
 * After some number of tokens have been exchanged, the GSSAPI implementations at both ends inform 
 * their local application that a security context has been established."
 * On HTTP, the tokens are sent in the headers Authorization and WWW-Authenticate.
 * The content of the header Authorization is given to the method AccepSecContext. This method returns a token sent
 * in the header WWW-Authenticate. Before to call InitSecContext or AcceptSecContex, the client and the server call AcquireCred
 * In this version, the class<code>SSPAuthentication</code> call a windows service(Negoserver.exe), so we
 * can use Tomcat on a unix (linux is a unix).<br/>
 * Of course, Tomcat can also run on Windows, In this case, the service Negoserver must be on the same server.<br/>
 * To authenticate this windows service call the method 
 * <code>AuthenticateAsServer</code> of the class <code>NegotiateStream</code> of the version 2.0 of the dotnet Framework dotnet 
 * used by Negoserver.<br>
 * <ol>We choose to write the service with dotnet because
 * <li>we can use the class NegotiateStream et we can easily obtains the access token and the groups of the client 
 * la r�cup�ration imm�diate du jeton d'acc�s du client et des groupes associ�s.
 * <li>we have the possibility to use an asynchronous model to economize resources (number of threads and thus memory).
 * <li>we can maintain more easily than with native code.</ol>
 *
 * If more than one exchange between the browser and the server is required (NTLM), 
 * we add an instance of the class <c>SSPAuthentication</c> in a session.
 * With Kerberos there is only one exchange.
 * To translate the names of the tomcat roles, a java class <code>TranslationRolesIntoSids</code> call Negoserver.
 * It translates the names into Sids with twos lines of code:
 * <pre>
 *      <code>NTAccount temp = new NTAccount(new string(groups_with_headers, (int)position, (int)size_elem));
 *      si = (SecurityIdentifier)temp.Translate(typeof(SecurityIdentifier)); </code>
 * </pre>
 * so, We can find easily the roles of a client.We have only to compare these SIds to the sids in the acces token of the client.
 * We have not to seach with each authentication.<br/>
 * An instance of this class is added to a context, not to a session.
 * To avoid the deadlock, Tomcat and Negoserver are never together reading or writing.
 * To avoid to block tomcat or the server, timeout are defined.
 * To prevent a lack of available ports wich can be in a status TIMLE_WAIT for 4 minutes 
 * after to be closed, we reuse, if it is possible, the connections TCP. So, the class <code>ConnectionTcpPool</code>.<p> 
 *
 * The possibility has been added to not use SPNEGO, but directly NTLM, if we set the parameter <code>onlyntlm</code>.
 * This parameter is not recommended, but with some clients...<br/>
 *
 * It is possible to use SSL betwen Tomcat and Negoserver. If tomcat is running on Windows, 
 * Negoserver is running on the same server. So, it is only interresting whentomcat is runnig on Unix.
 * The information on the net are not secrets but could be modified on the net.
 * But Now, the hubs are no more used. On the net, we use switchs; So, except the brodcasted packets (like ARP), 
 * we can only read or modify the packets on the switchs or the routers.
 * But when we are working with specialists of the security... 
 *
 * <br/><b>Configuration:</b><br/>
 * <ol> <b>Tomcat 5.5</b>: 
 * <li>The jar file must be in the directory <code>/server/lib</code> of tomcat.
 * <li>The file <code>webapps/<application>/META-INF/context.xml</code> of the application must contain:
 * <pre>
 *    &lt;Valve className="fr.doume.cli.authenticator.SSPAuthenticator" />
 *    &lt;Realm className="fr.doume.cli.realm.WindowsRealm" />
 *
 *     &lt;!-- By default 127.0.0.1 . Can be an IP address or the DNS name of the server.
 *     &lt;Parameter name="serveraddress" value="127.0.0.1" override="false"/>
 *     -->
 *    This parameter is REQUIRED when tomcat is running on UNIX.
 *    Must be the DNS name when we use SSL between and the Windows service
 *    With SSL, Tomcat has to known  the DNS name of the address or the DNS Name of the Windows server
 *    &lt;!--
 *    &lt;Parameter name="serveraddress" value="negoserver.test.net" override="false"/>
 *    -->
 *     &lt;!-- By default 21000. 
 *     &lt;Parameter name="port" value="21000" override="false"/>
 *     -->
 *     &lt;!-- By default, not used. This parameter is not recommended
 *     &lt;Parameter name="onlyntlm" value="" override="false"/>
 *     -->
 *     &lt;!-- This two parameters as required only when SSL is used between Tomcat and the Windows service.
 *     &lt;Parameter name="truststorepath" value="C:\Users\minou\Downloads\makecert\trust_w543118a.jks" override="false" />
 *     &lt;Parameter name="truststorepassword" value="changeit" override="false" />
 *     -->
 *     &lt;!-- Time life in ms of the translation of the names of roles into Sids: By Default without limit --> 
 *     &lt;!--
 *     &lt;Parameter name="timelifemaprolesintosids" value="-1" override="false" />
 *     -->
 *     -->
 *     &lt;!-- By default, not used
 *     &lt;Parameter name="nogroupsinad" value="" override="false" />
 *     -->
 * 
 * </pre>
 * <li> The web.xml must contain information on the protected zone(s) of the application: For instance:
 * <pre>
 *   &lt;security-constraint>
 *     &lt;display-name>Managers Security Constraint</display-name>
 *     &lt;web-resource-collection>
 *        &lt;web-resource-name>Protected Area Gestionnaires&lt;/web-resource-name>
 *         &lt;!-- Define the context-relative URL(s) to be protected -->
 *        &lt;url-pattern>/manage/*&lt;/url-pattern>
 *      &lt;/web-resource-collection>
 *     &lt;auth-constraint>
 *        &lt;!-- Anyone with one of the listed roles may access this area -->
 *        &lt;role-name>DOMAINADNAME\Gestionnaires&lt;/role-name>
 *        &lt;role-name>DOMAINADNAME\pipo&lt;/role-name>
 *     &lt;/auth-constraint>
 *   &lt;/security-constraint>
 *   &lt;security-constraint>
 *     &lt;display-name>Staff Security Constraint&lt;/display-name>
 *     &lt;web-resource-collection>
 *        &lt;web-resource-name>Staff Protected Area&lt;/web-resource-name>
 *         &lt;!-- Define the context-relative URL(s) to be protected -->
 *        &lt;url-pattern>/staff/*&lt;/url-pattern>
 *      &lt;/web-resource-collection>
 *     &lt;auth-constraint>
 *        &lt;!-- Anyone with one of the listed roles may access this area -->
 *        &lt;role-name>DOMAINADNAME\Chefs&lt;/role-name>
 *     &lt;/auth-constraint>
 *   &lt;/security-constraint>
 *   &lt;security-constraint>
 *     &lt;display-name>Authenticated Users&lt;/display-name>
 *     &lt;web-resource-collection>
 *        &lt;web-resource-name>Authenticated Protected Area&lt;/web-resource-name>
 *         &lt;!-- Define the context-relative URL(s) to be protected -->
 *        &lt;url-pattern>/app/*&lt;/url-pattern>
 *      &lt;/web-resource-collection>
 *     &lt;auth-constraint>
 *        &lt;!-- Authenticated users may access this area -->
 *        &lt;role-name>*&lt;/role-name>
 *     &lt;/auth-constraint>
 *   &lt;/security-constraint>
 *
 *   &lt;login-config>
 *     &lt;auth-method>BASIC&lt;/auth-method>
 *     &lt;realm-name>Example Windows Authentication Area&lt;/realm-name>
 *   &lt;/login-config>
 *       
 *   &lt;!-- Security roles referenced by this web application -->
 *   &lt;security-role>
 *     &lt;role-name>users&lt;/role-name>
 *   &lt;/security-role>
 *   &lt;security-role>
 *     &lt;role-name>DOMAINADNAME\Domain users&lt;/role-name>
 *   &lt;/security-role>    
 *   &lt;security-role>
 *     &lt;role-name>tomcat&lt;/role-name>
 *   &lt;/security-role>
 *   &lt;security-role>
 *     &lt;role-name>DOMAINADNAME\Gestionnaires&lt;/role-name>
 *   &lt;/security-role>  
 *   &lt;security-role>
 *     &lt;role-name>DOMAINADNAME\Chefs&lt;/role-name>
 *   &lt;/security-role>  
 *   &lt;security-role>
 *     &lt;role-name>DOMAINADNAME\pipo&lt;/role-name>
 *   &lt;/security-role>
 * </pre>
 *
 *  Remember, the order of the elements in a file <code>web.xml</code> is:
 * <pre>
 *           icon
 *           display-name
 *           description
 *           distributable
 *           context-param
 *           filter
 *           filter-mapping
 *           listener
 *           servlet
 *           servlet-mapping
 *           session-config
 *           mime-mapping
 *           welcome-file-list
 *           error-page
 *           taglib
 *           resource-env-ref
 *           resource-ref
 *           security-constraint
 *           login-config
 *           security-role
 *           env-entry
 *           ejb-ref
 *           ejb-local-ref
 * </pre>
 * </ol>
 * <ol> <b>Tomcat 6</b>: 
 * <li>The jar file must be in the directory <code>/server/lib</code> of Tomcat 6.
 * <li>The content to add in the files web.xml and context.xml for Tomcat 6 are the same than for Tomcat 5.5.
 * <li> <ol> With tomcat 5.5 the logs use the java class given by commons logging. We find
 *           these classes in <code>common/lib/commons-looging-api.jar</code>.
 *           These classes are not given by default for Tomcat 6
 *           There are two solutions:
 *     <li> Replace the  two imports with <code>org.apache.commons.logging</code> by <code>org.apache.juli.logging</code>
 *          and complie the java code.(frdoumesspitc6v2.jar)
 *     <li> Use the jar given for tomcat 5.5 (frdoumesspitc5v2.jar) and add commons-logging-api.jar 
 *          in the directory <code>/bin</code> of Tomcat 6. 
 *          You can download this jar file from <code>http://commons.apache.org</code> 
 *     </ol> 
 * </ol>
 */
public class SSPAuthenticator
    extends AuthenticatorBase {
    private static Log log = LogFactory.getLog(SSPAuthenticator.class);

  // ----------------------------------------------------- Static Variables
 /**
   * Key Session attribute 
   *
   */
  private final static String SSPIAUTHENTICATION_KEY = 
    "fr.doume.cli.spnego.SSPIAuthentication";
 
 /**
   * Key Context attribute 
   * "fr.doume.cli.authenticator.AuthenticationContext".
   */
  private final static String CONTEXT_KEY =
    "fr.doume.cli.authenticator.AuthenticationContext";

  private final static String ALREADYCALLED_KEY =
    "fr.doume.v3.authenticator.NewCall";
  
  private final static String LOGIN_KEY =
    "fr.doume.cli.authentticator.Login";

    /**
     * Descriptive information about this implementation.
     */
  protected static final String info =
      "fr.doume.cli.authenticator.SSPAuthenticator/1.0";


    // --------------------------------------------------------- Public Methods

    /**
     * Return descriptive information about this Valve implementation.
     */
  public String getInfo() {

      return (info);

  }
    /**
     * This method is called when the client do a request with an uri wich is in a protected zone.
     * Returns true when the client is authenticated, else false with the stus code 401.
     * The first time, this method is called for an apllication, authenticate cerate an AuthenticationContext,
     * reads the parameters defined in the file context.xml and create a singleton Parameters, and try to 
     * translate the names of the roles tomcat defined in the file web.xm via the class TranslateNamesIntoSids which
     * sends the names to the Windows service. Negoserver responds with the Sids.
     * Then authenticate takes the header Authorization, decodes it from Base64, and sends it to the windows service wich use the dotnet 
     * class NegotiateStream. Negoserver sens also the name of the client, and the Sids translated from the names of roles 
     * that are in the access token.
     * To configure Firefox or IE, see the file <code>configure.txt</code>
     * The browser (Firefox or IE) try to use Kerberos, only when the uri uses a name that can be found in Active Directory.
     * On Windows, Negosesrver must be running on the same server as tomcat. Negosesrver uses the account SYSTEM or Network Service.
     * A server (or worksation) has an account with a Service Principal Name by default (HOST/<nameoftheserver>). The browser search 
     * a Service Principal Name HTTP/<nameoftheserver>. The KDC search a Service Principal Name HTTP(or HOST)/<nameoftheserver>.
     * If you want to use another name, you can define a CNAME in your DNS. The browser will use the translation (entry A in your DNS) 
     * to search the Srevice Principal Name.
     * If Tomcat is running on Unix, You must define an entry in your DNS and add HTTP/<this entry DNS> as Service Principal Name
     * to the account uses to run NegoServer. If Negoserver is running with SYSTEM or Network Service, you add this Service Principal
     * Name to the server account where negoserver is running.
     * For instance, if SYSTEM or Network Service is the account of Negoserver on the server windowssrv.test.net and if
     * Tomcat bis running on unixsrv.test.net, I can use the command:
     *  <pre>
     *            <code> setspn -A HOST/unixsrv.test.net TEST\windowssrv </code>  
     *                 or
     *            <code> setspn -A HTTP/unixsrv.test.net TEST\windowssrv </code>    
     *  </pre> 

     * If there is no SPN HTTP/nameoftheserver found in Active Directory, Fierfox or IE will use NTLM.
     * For more information, see <code>configure.txt</code>
     *   
     *  
     * @param request Requset to process
     * @param response Response to send
     * @param config   Not use in this Authenticator
     *
     * @exception IOException si une erreur d'E/S a lieu.
     */
    public boolean authenticate(Request request,
                             HttpServletResponse response,
                                LoginConfig config)
      throws IOException
          //par sendError
    {
    
      //setSecurePagesWithPragma(false);
      //boolean firsrtRoundTrip = true;

        // Is someone already authenticated?
      Principal principal = request.getUserPrincipal();
        
      if (principal != null) {
        if (log.isDebugEnabled())  log.debug("Already authenticated '" + principal.getName() + "'");
        
        HttpSession session = request.getSession(false);
        if (session != null)
        {
          if (session.getAttribute(LOGIN_KEY) != null)
          {
            session.setAttribute(LOGIN_KEY,null);
            return true;
          }
        }

        // If the header Authorization is present 
        //  and if it is not the firs calll during the request processing, mustAuthenticate returns true 
        if ( ! mustAuthenticate(request) )
        {
          return true;
        }
        if (log.isDebugEnabled())  log.debug("Header Authorization given by the navigator: we must authenticate the client");
      }
           
      if (log.isDebugEnabled())
      {
        log.debug("Entry in  authenticate. Principal does not exist or the browser send the header Authorization: authentication is required"); 
      }
 
      Realm realm = context.getRealm();
      if( realm == null ){
        log.warn("No  Realm is configured or defined");
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE );
        return false;
      }
      if (log.isDebugEnabled()) log.debug("realm : " + realm);    
      
      TranslationRolesIntoSids trd = null;
      AuthenticationContext ac = null;
      
      Context ctx = request.getContext();
      ac  = getAuthenticationContext(ctx);
      if (ac == null){
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE );
        return false;
      }

      Parameters p = ac.getParameters();

      if (p.areGroupsInAd() || p.isLoginWithAd())
      {
        trd  = getTranslationRolesIntoSids(ctx, ac);
        if (trd == null){
          response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE );
          return false;
        }
      }
           
      if (request.getHeader("Authorization") == null)
      {
        setHeadersWWWAuthenticate(p, response);
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
      }
      
       // The tomcat roles are translated into Windows SIDs( security identifiers)
      String clientName = null;
      log.debug("before getSSPAuthentication")	;
      
      if ( p.isOnlyKerberos() )     
      {
        if (isNtlmAuthentication(p, request))
        {
          if (log.isDebugEnabled()) log.debug("Ntlm Authentication");
          //response.setHeader("WWW-Authenticate", p.NEGOTIATE);
          response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
          return false;
        }
      }
      
      SSPAuthentication sspauth = getSSPAuthentication(request, ac);

      try{
        getHeaderAuthorizationAndSetWWW_Authenticate(request,response,sspauth, p);
      }
      catch(FormatBase64Exception e)
      // The token sent by the client is not in the format Base64
      {
        RemoveSSPAuthenticationFromContainer(request, ac);
        if (log.isDebugEnabled()) log.debug(e.getClass().getName() + " : " + e.getMessage());       
        
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
        return false;        
      }
      
      
      if (sspauth.isEstablished())
      {
        RemoveSSPAuthenticationFromContainer(request, ac);

        if (log.isDebugEnabled()) log.debug("Authentication is established");

        //try/catch required by java. 
        // Exception is not possible cf( the precondition isEstablished() in SSPAuthentication)
        try{         
          clientName = sspauth.getUserName();
        }
        catch (SSPException e) { log.fatal("This Exception could not exist: " + e.getMessage()); }
      }
      else if (sspauth.isFailed())
      {
        //error: remove SSPAuthentication from the session
        RemoveSSPAuthenticationFromContainer(request, ac);

        if (log.isDebugEnabled()) log.debug("authentication is failed");  

        if (p.canUserChooseAccount())
        {
          response.setHeader("WWW-Authenticate", p.NEGOTIATE);
        }             
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
      }
      else
      { //another exchange bettween the server and the client is requuired => send the status code 401
        //SSPAuthentication is not removed from the session
        if (log.isDebugEnabled()) 
            log.debug("Authentication is neither failed nor established: Another round between the client and the server is required");

        setSSPAuthenticationInContainer( request, sspauth, ac);
        
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
      } 
        
 
        //sspauth.isEstablished
      if (clientName != null)
      {
        if (p.isUserNameWithoutDomainName())
        {
          //SSPI sends as name like DomaiName\ClientName
          //We want to have only ClientName
          int position = clientName.indexOf('\\');
          String clientTemp = clientName;
          if (clientTemp.length() > position){
            clientTemp = clientTemp.substring(position+1);
          }
          if (log.isDebugEnabled()){
            if (log.isDebugEnabled()) log.debug("clientName :" + clientTemp);
          }
          clientName = clientTemp;
        }
   
        principal = getPrincipal(p, clientName, trd, sspauth);
  
        if (principal != null)
        {
          //Create a session?
          HttpSession session = request.getSession(p.mustCreateSession());
          //register the principal
          register(request, response, principal,
                 "DIGEST",
                 clientName, clientName);
          if (log.isDebugEnabled()) log.debug("the client is now registered");
          request.setAttribute(ALREADYCALLED_KEY, "1");         
          return (true);
        }
        else
        {
          //We cannot be here, if a realm is defined
          if (log.isDebugEnabled())
              log.debug("User authenticated but we cannot create a new GenericPrincipal.");
          response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
          return false;
        }
      } //if (clientName != null)
      if (log.isDebugEnabled()) log.debug("clientName == null && authenticated!!! curious..."); 
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
      return (false); 
    }

    // --------------------------------------------------------- Private Methods
    private Principal getPrincipal(Parameters p, String clientName, TranslationRolesIntoSids trd, SSPAuthentication sspauth)
    {

        Principal  principal = null;
        ArrayList<String> roles = null;

        Realm realm;
        try
        {
          realm = context.getRealm();
        }
        catch(Exception e)
        {
          if (log.isDebugEnabled()) log.debug("No realm defined!");
          return null;
        }
        if (realm == null)
        {
          if (log.isDebugEnabled()) log.debug("No realm defined!");
          return null;
        }

        if(p.areGroupsInRealm())
        {
          log.debug("groups are given by a realm");
          principal = realm.authenticate(clientName, clientName);
          if (principal == null) log.debug("principa null with " + clientName);
        }
        if (p.isWithCommonRole())
        {
          roles = new ArrayList<String>();
          roles.add(p.getCommonRole());
        }
        if (p.areGroupsInAd())
        {
          boolean[] areGroupsInAccessToken = sspauth.areRolesInAccessToken();
          if (roles == null) roles = new ArrayList<String>();
          
          String[] groupes = trd.getGroups();
          for (int k = 0; k <groupes.length ; k++)
          {
            if ( ! areGroupsInAccessToken[k] ) continue;
            roles.add(groupes[k]);
          }
          log.debug("The roles are Windows groups");
        }//areGroupsInAd
        
        if (roles != null)
        {
          log.debug("There are some roles not given by a non windows realm");
          if (principal != null)
          {
            if (principal instanceof GenericPrincipal)
            {
              String[] sroles = ((GenericPrincipal)principal).getRoles();
              for( String role : sroles)
              {
                roles.add(role);
              }
            }
            else
            {
              log.error("principal is not a GenericPrincipal!!!!, so we cannot add the common role and the windows groups to the principal");
            }
          }//roles != null

          principal = new GenericPrincipal( context.getRealm(),
                         clientName,
                         clientName,
                         roles);        
        }
        return principal;
   
    }

    /**
    * Called by authenticate when the client is already authenticated.
    * With the verb POST, IE send the header Authorization. We must authenticate again, else 
    * the browser could not send (NTLM) the request.
    */ 
    private boolean mustAuthenticate(Request request)
    {
      // when here, principal is not null.
      if (request.getHeader("Authorization") == null)
      {
        if (log.isDebugEnabled()) log.debug("HeaderAuthorization is not present");

        return false;
      }
      if ( request.getAttribute(ALREADYCALLED_KEY)  != null )
      //the method authenticate is called two times by tomcat during a request
      {
        if (log.isDebugEnabled()) log.debug("It is not the first call of the method authenticate by tomcat");
        return false;
      }
      else
      {
        if (log.isDebugEnabled()) log.debug("It is the first call of the method authenticate by tomcat");      
      }
      return true;
    }
    
    private AuthenticationContext getAuthenticationContext( Context ctx )
    {
      StandardContext std = (StandardContext)ctx;
      ServletContext srvctx = std.getServletContext();
      Parameters p = null;
      
      //returns the singleton if it is in the context,
      AuthenticationContext ac = (AuthenticationContext)srvctx.getAttribute(CONTEXT_KEY);
      if (ac != null) return ac;
      
      //else create the singleton Parameters and then create the instance of AuthenticationContext
      p = new Parameters( srvctx );     
      ac = new AuthenticationContext(p);

      if ( ! p.areGroupsInAd() )
      {
        srvctx.setAttribute(CONTEXT_KEY, ac);
      }

      return ac;
    }
    
	   /**
     * Get an instance of TranslationRolesIntoSids from AuthenticationContext.
     * If this instance is too old, another instance will be created.
     */
    private TranslationRolesIntoSids getTranslationRolesIntoSids( Context ctx, AuthenticationContext ac )
    {
      Parameters p = null;

      TranslationRolesIntoSids trn = ac.getTranslationRolesIntoSids();
      
      if (trn != null)
      {
        p = ac.getParameters();
        int  lifetimeTranslationMs = p.getTimeLifeMappingRolesIntoSids();
        if ( lifetimeTranslationMs < 0 ) // infinite
        {
          if (log.isDebugEnabled()) log.debug("get the instance of the current TranslationRolesIntoSids from the Context");
          return trn;
        }
        
        Date now = new Date();
        Date creationDate = trn.getDate();
        if ( now.getTime() - creationDate.getTime() < lifetimeTranslationMs )
        {        
          if (log.isDebugEnabled()) log.debug("get the instance of the current TranslationRolesIntoSids from the Context");
          return trn;
        }
        else
        {
          if (log.isDebugEnabled()) log.debug("life time of TranslationRolesIntoSids > Max lifetimeTranslationMs");
          //A new instance of TranslationRolesIntoSids is required
        }
      }
      
      trn = createTranslationNamesToSids( ctx, ac);
      return trn;
    }
    
    /**
    * Create a new instance of TranslationRolesIntoSids, call translateRolesIntoSids.
    * Put this instance in AuthenticationContext and put the singleton AuthenticationContext 
    * in the context
    */ 
    private TranslationRolesIntoSids createTranslationNamesToSids( Context ctx, AuthenticationContext ac  )
    {
      StandardContext std = (StandardContext)ctx;
      ServletContext srvctx = std.getServletContext();

      TranslationRolesIntoSids trd = new TranslationRolesIntoSids( ac );
      if (translateRolesIntoSids( trd, ctx) ) 
      {
        ac.setTranslationRolesIntoSids( trd );
        srvctx.setAttribute(CONTEXT_KEY, ac);
        return trd;
      }
      else return null;
    }
    

     /**
     * get the names of groups via <c>context.findSecurityRoles()</c><br>
     * Then try to translate the names into sids with an instance of the class TranslationRolesIntoSids.<br>
     */
    private boolean translateRolesIntoSids( TranslationRolesIntoSids trd, Context ctx)
    {
       String[] groupes = ctx.findSecurityRoles();
       
      if (log.isDebugEnabled())
      {
        log.debug("Count of groups defined in the context : " + groupes.length); 
        for (int ind = 0; ind < groupes.length ; ind++){
          log.debug(groupes[ind]);
        }
      }

      try
      {
        int nbnulls = 0;
        int sidsCount = trd.getSidsCount(groupes);
        if (log.isDebugEnabled())
        {
          log.debug("roles have been translated into sids");
          log.debug("Count of sids defined in the context : " + sidsCount); 

        }
      }
      catch(SSPException e)
      {
        log.fatal("SSPexception in getSids  : The mapping from the roles to the Active Directory's groups cannot be build " + e.getMessage());
        //response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE );
        return false;
      }
      return true;		

    }
 
     /** 
     * Search an instance of SSPAuthentication from the session, else create a new instance 
     * With NTLM, more than one exchange is required.
     */
    private SSPAuthentication 
      getSSPAuthentication( Request request,/* Response response,*/ AuthenticationContext ac ) 
    {
      String address = request.getRemoteAddr() + ":" + request.getRemotePort();
      SSPAuthentication SSPIauth = null;

      HashMapWithTimeOut<String, SSPAuthentication> ctxHashMap = ac.getHashMapWithTimeOut();
      if ( ctxHashMap != null)    
      {
        SSPIauth = getSSPAuthenticationFromConnectionTcp( request, ctxHashMap);
      }
      else
      { 
        SSPIauth = getSSPAuthenticationFromSession(request);
      }
      if (SSPIauth == null)
      {		
        if (log.isDebugEnabled()) log.debug ( "Create a new instance of SSPIAuthentification" );
        SSPIauth = new SSPAuthentication(ac);
      }

      return SSPIauth;
    } 
    

     /**
     * Get the {@link SSPAuthentication} lfrom the current session.
     */
    private SSPAuthentication getSSPAuthenticationFromSession(Request request)
    {
      
      HttpSession session = request.getSession(false);   
      if ( session == null ) return null;

      SSPAuthShadow shadow = (SSPAuthShadow)session.getAttribute(SSPIAUTHENTICATION_KEY);
      if (shadow == null) return null; 
      return shadow.ssp;

      //return (SSPAuthentication)session.getAttribute(SSPIAUTHENTICATION_KEY);   
    }

    private SSPAuthentication getSSPAuthenticationFromConnectionTcp( Request request, HashMapWithTimeOut<String, SSPAuthentication> ctxHashMap)
    {
      String address =  request.getRemoteAddr() + ":" + request.getRemotePort();    
      return ctxHashMap.get(address);
    }

    /**
     * Set a  {@link SSPAuthentication} into the current session.
     */
    private void setSSPAuthenticationInContainer(Request request, SSPAuthentication sspauth, AuthenticationContext ac)
    {
      HashMapWithTimeOut<String, SSPAuthentication> ctxHashMap = ac.getHashMapWithTimeOut();
      if ( ctxHashMap != null )
      {
        String client = request.getRemoteAddr() + ":" + request.getRemotePort();
        ctxHashMap.put(client, sspauth);
        if (log.isDebugEnabled()) log.debug ( "set SSPAuthentication in a HashMapWithTimeOut" );
      }
      else
      {
        setSSPAuthenticationInSession(request, sspauth);
      }
      return;
   }  

    private void setSSPAuthenticationInSession(Request request, SSPAuthentication sspauth)
    {
        
      HttpSession session = request.getSession();      
      SSPAuthShadow shadow = new SSPAuthShadow();
      shadow.ssp = sspauth;
      session.setAttribute(SSPIAUTHENTICATION_KEY, shadow);
      
      if (log.isDebugEnabled()) log.debug ( "set SSPAuthentication in a session" );
      return;
    }  

    /**
    * Remove the link between {@link SSPAuthentication} and the current session. 
    * Called when the authentication is finished. 
    */
    private void RemoveSSPAuthenticationFromContainer(Request request, AuthenticationContext ac)
    {
      HashMapWithTimeOut<String, SSPAuthentication> ctxHashMap = ac.getHashMapWithTimeOut();
      if ( ctxHashMap != null)
      {
        RemoveSSPAuthenticationFromConnectionTcp( request, ctxHashMap ); 
      }
      else RemoveSSPAuthenticationFromSession( request );
    }
    
    private void RemoveSSPAuthenticationFromSession(Request request)
    {
      HttpSession session = request.getSession(false);   
      if ( session == null ) return;
      if ( session.getAttribute(SSPIAUTHENTICATION_KEY) != null )
      {
        session.removeAttribute(SSPIAUTHENTICATION_KEY);
      }
      return;
    }

    private void RemoveSSPAuthenticationFromConnectionTcp( Request request, HashMapWithTimeOut<String, SSPAuthentication> ctxHashMap)
    {
      String address =  request.getRemoteAddr() + ":" + request.getRemotePort();    
      ctxHashMap.remove(address);
      return;
    }


  /**
   *
   * <ol>
   *   <li>Verify if the clent sent a header "Authorization".
   *       If the header starts with "Negotiate", calls <c>fromAuthorizationToWwwAuthenticate<c/>   
   *   <li> If the result of the method <c>fromAuthorizationToWwwAuthenticate<c/>  is non null
   *       it will be used in the header "WWW-Authenticate" of the response.
   * </ol>
   */
    private void getHeaderAuthorizationAndSetWWW_Authenticate( HttpServletRequest request,
                              HttpServletResponse response,
                              SSPAuthentication sspauth , Parameters p) 
           throws FormatBase64Exception
    {
      
      log.debug( "authentication SPNEGO: remote address: " + request.getRemoteAddr() );
      
      String authorization = request.getHeader("Authorization");
      
      String negotiation = null;
      if (authorization != null)
      {
        negotiation = getAuthorizationType(authorization, p);
        if (negotiation == null)
        {
          authorization = null;
        }
      }
      
      if ( authorization != null ) {
        if (log.isDebugEnabled()) log.debug( "Token received from the client: " + authorization );
      }
      
      String responseToken = fromAuthorizationToWwwAuthenticate( authorization ,sspauth, p);

      if ( responseToken != null ) {
        response.setHeader("WWW-Authenticate", negotiation + " " + responseToken);
        if (log.isDebugEnabled()) log.debug( "Sending response token: {}" + responseToken );
        return;
      }
      if ( sspauth.isEstablished() || sspauth.isFailed()  )
      {
        if (log.isDebugEnabled()) log.debug("Authentication is ended (failed or established), with a a request without  the haeder NEGOTIATE." );
        return;        
      } 
      else 
      {
        // Start of the negotiation between the server and the client
        if (log.isDebugEnabled()) log.debug(" The client did not send Authorization header: so NEGOTIATE is sent with the code 401");
        setHeadersWWWAuthenticate(p, response);
      }
    }
   
  private void setHeadersWWWAuthenticate(Parameters p, HttpServletResponse response)
  {
     if ( p.canUserChooseBetwweenSpnegoAndNntlm())
    {
      if (log.isDebugEnabled()) log.debug(" The client can choose between Negotaite and NTLM");
      response.setHeader("WWW-Authenticate", "Negotiate");
      response.addHeader("WWW-Authenticate", "NTLM");
    }
    else
    {
      if (log.isDebugEnabled()) log.debug(" The client cannot choose between Negotiate and NTLM");
      response.setHeader("WWW-Authenticate", p.NEGOTIATE);
    }

  }
   /**
   *
   * <ol>
   *   <li>Verify if the clent sent a header "Authorization" with "Negotiate" or "NTLM"
   *       If the header starts with "Negotiate", calls <c>fromAuthorizationToWwwAuthenticate<c/>   
   *   <li> If the result of the method <c>fromAuthorizationToWwwAuthenticate<c/>  is non null
   *       it will be used in the header "WWW-Authenticate" of the response.
   * </ol>
   */

  private String getAuthorizationType(String authorization, Parameters p)
  {
    
    if (authorization != null)
    {
      if ( p.canUserChooseBetwweenSpnegoAndNntlm())
      {
        if ( authorization.startsWith("Negotiate") )
        {
          return "Negotiate";
        }
        else if ( authorization.startsWith("NTLM") )
        {
          return "NTLM";
        }
      }
      else if ( authorization.startsWith(p.NEGOTIATE) )
      {
        return p.NEGOTIATE;
      }
      return null;
    }
    return null;
  }

    /**
     * Try to decode from base 64 the string sent by the client. The send the result to  
     * <c>fromInitSecSecurityToAcceptSecurity</c>.
     * encode into Base64 the result of this last method.
     */
    private String fromAuthorizationToWwwAuthenticate( String authorization, SSPAuthentication sspauth, Parameters p ) 
    throws FormatBase64Exception
    {      
      if (authorization != null && authorization.startsWith(p.NEGOTIATE)) 
      {
        if (log.isDebugEnabled()) log.debug("Authorization != null && Authorization starts With NEGOTIATE");
          // the prefix "Negotiate " is removed from the string
        String s = authorization.substring(p.NEGOTIATE.length()+1);
        
        byte[] fromInitSecSecurityBase64 = s.getBytes();
        if (log.isDebugEnabled()) 
        {
          log.debug("count of bytes received from the client: " + fromInitSecSecurityBase64.length);
        }
   
        byte[] fromInitSecSecurity = null;
        
        //Remark : fromInitSecSecurity is not null, else a FormatBase64Exception has been thrown
        fromInitSecSecurity = Base64.decodeFrom64(fromInitSecSecurityBase64);
        if (log.isDebugEnabled()) 
        {
          log.debug("Count of decoded bytes received from the client after the translation from Base 64 : " + fromInitSecSecurity.length);
        }
        
        //envoie au broyage par SSPI (acceptSecContext) des octets 
        // broy�s par l'appel SSPI du client (initSecContext)
        // puis renvoie les octets resultats de ce broyage
        byte[] fromAcceptSecSecurity = null;
        fromAcceptSecSecurity = fromInitSecSecurityToAcceptSecurity( fromInitSecSecurity , sspauth);
        
        if ( fromAcceptSecSecurity == null ){
          if (log.isDebugEnabled()) log.debug(" The array of bytes sent by SSPI == null");
          return null;
        }
        byte[] fromAcceptSecSecurityBase64 = null;
        fromAcceptSecSecurityBase64 = Base64.encodeTo64(fromAcceptSecSecurity);
        if (log.isDebugEnabled()) 
        {
          log.debug("count of bytes sent to the client: " + fromAcceptSecSecurity.length);         
          log.debug("count of bytes sent (base 64) to the client : " + fromAcceptSecSecurityBase64.length); 
        }
   
        // REMARK : the return will be in the header WWW_Authenticate  
        return new String(fromAcceptSecSecurityBase64);
        
      }
      return null;
      
    }
    
   
    /**
     * Receive the bytes sent by the client .(result of <c>initSecContext</c>)
     * Return the bytes return by  <c>acceptSecContext</c>
     */
    private byte[] fromInitSecSecurityToAcceptSecurity(byte[] fromInitSecSecurity, SSPAuthentication sspauth)
    {

      // preconditions
      if (fromInitSecSecurity == null) return null;
      
      byte[] fromAcceptSecSecurity = null;    
      try
      {
        if (log.isDebugEnabled()) log.debug("before sspauth.acceptSecContext");
        fromAcceptSecSecurity = sspauth.acceptSecContext(fromInitSecSecurity);

        if (log.isDebugEnabled()){
          if ( fromAcceptSecSecurity != null) log.debug("count of bytes received from acceptSecContext : " + fromAcceptSecSecurity.length);
        }
        
        if (sspauth.isEstablished())
        {
          if (log.isDebugEnabled()){
            log.debug("Authenticated user : " + sspauth.getUserName());
            log.debug("Security Service Provider Interface used : " + sspauth.getSSP());
          }	
        } 
        else
        // NTLM
        {
          if (log.isDebugEnabled()) log.debug("Another cycle initSecContext/acceptSecContext is required");
        }
      }
      catch (SSPException e){
        //Authentication failed
        fromAcceptSecSecurity = null;
        if (log.isDebugEnabled()) log.debug("SSPIAuthentication : " + e.getMessage() );
      }

      if (fromAcceptSecSecurity == null)
      {
        if (log.isDebugEnabled()) log.debug("array of bytes sent by acceptSecContext (SSPI server) is null");
      //return null;
      }
      return fromAcceptSecSecurity;

    }//fromInitSecSecurityToAcceptSecurity
   
    private boolean isNtlmAuthentication( Parameters p, Request request)
    {
      String authorization = request.getHeader("Authorization");
      if (authorization != null && authorization.startsWith(p.NEGOTIATE + " TlRMTVNTUA")) return true;
      return false; 
    }

  protected String getAuthMethod()
  {
    return "DIGEST";
  }
  
  
    protected Principal doLogin(Request request, String username,String password) 
      throws ServletException 
    {
      if ( (username == null) || (username.length()  == 0) || (password == null) || (password.length() == 0)  || (request == null) )
      {
        throw new ServletException("username or password or request is unknown"); 
      }
      //Principal p = context.getRealm().authenticate(username, password);

      if (log.isDebugEnabled()) log.debug("Authentication via doLogin");

      TranslationRolesIntoSids trd = null;
      AuthenticationContext ac = null;
      
      Context ctx = request.getContext();
      ac  = getAuthenticationContext(ctx);
      if (ac == null)
      {
        if (log.isDebugEnabled()) log.debug("Authentication Context cannot be created");
        throw new ServletException("Authentication Context cannot be created");
      }

      Parameters p = ac.getParameters();

      Principal principal = null;
      Realm realm;
      try
      {
        realm = context.getRealm();
      }
      catch(Exception e)
      {
        if (log.isDebugEnabled()) log.debug("No realm defined!");
        throw new ServletException("no realm defined.");
      }
      
      if (p.isLoginWithoutAD())
      {
        //
        principal = realm.authenticate(username, password);
        if (principal != null)
        {
          HttpSession session = request.getSession(p.mustCreateSession());
          if (log.isDebugEnabled()) log.debug("User authenticated with login/pawword");
          if (session != null) session.setAttribute(LOGIN_KEY, "1");
        }
        return principal;
      }
      
      if (p.areGroupsInAd())
      {
        trd  = getTranslationRolesIntoSids(ctx, ac);
        if (trd == null)
        {
          if (log.isDebugEnabled()) log.debug("No translation of the roles into SIDs");
          throw new ServletException("No translation of the roles into SIDs");
        }
      }
      
      String clientName= null;
      SSPAuthentication sspauth = new SSPAuthentication(ac);
      try
      {
        sspauth.login( username, password);
      }
      catch( SSPException e)
      {
        throw new ServletException(e.getMessage() );
      }
      if (sspauth.isEstablished())
      {
        if (log.isDebugEnabled()) log.debug("Authentication is established");
        //try/catch required by java. 
        // Exception is not possible cf( the precondition isEstablished() in SSPAuthentication)
        try
        {         
          clientName = sspauth.getUserName();
        }
        catch (SSPException e) { log.fatal("This Exception could not exist: " + e.getMessage()); }
      }
      else
      {
        if (log.isDebugEnabled()) log.debug("Authentication is failed"); 
              throw new ServletException(sm.getString("authenticator.loginFail"));

      }
      if (clientName != null)
      {
        if (p.isUserNameWithoutDomainName())
        {
          //SSPI sends as name like DomaiName\ClientName
          //We want to have only ClientName
          int position = clientName.indexOf('\\');
          String clientTemp = clientName;
          if (clientTemp.length() > position){
            clientTemp = clientTemp.substring(position+1);
          }
          if (log.isDebugEnabled()){
            if (log.isDebugEnabled()) log.debug("clientName :" + clientTemp);
          }
          clientName = clientTemp;
        }

        if (p.areGroupsInAd())
        {  
          boolean[] areGroupsInAccessToken = sspauth.areRolesInAccessToken();
          ArrayList<String> roles = new ArrayList<String>();
          
          String[] groupes = trd.getGroups();
          for (int k = 0; k <groupes.length ; k++)
          {
            if ( ! areGroupsInAccessToken[k] ) continue;
            roles.add(groupes[k]);
          }
          // Creation of the principal
          principal = new GenericPrincipal( realm,
                         clientName,
                         clientName,
                         roles);        
        }
        else
        {
          //authentification aupr�s du realm configur� pour r�cup�rer un principal avec les roles.
          principal = realm.authenticate(clientName, clientName);
   
        }
        if (principal != null)
        {
          HttpSession session = request.getSession(p.mustCreateSession());
          if (log.isDebugEnabled()) log.debug("the client is now authenticated");
          if (session != null) session.setAttribute(LOGIN_KEY, "1");
          return principal;
        }
        else
        {
          //We cannot be here, if a realm is defined
          if (log.isDebugEnabled())
          {
              log.debug("User authenticated but we cannot create a new GenericPrincipal.");
              throw new ServletException(sm.getString("authenticator.loginFail"));
          }
        }
      } //if (clientName != null)
      if (log.isDebugEnabled()) log.debug("clientName == null && authenticated!!! curious..."); 
              throw new ServletException(sm.getString("authenticator.loginFail"));
    }
    public void logout(Request request)
    throws ServletException
    {
      if (request == null) throw new ServletException(" request is null");
      super.logout(request);
      HttpSession session = request.getSession(false);
      if (session != null) session.invalidate();
    }
   // To add in AUthenticator with tc6 or jboss 6
    public void login(String username, String password, Request request)
    throws ServletException {
        Principal principal = doLogin(request, username, password);
        register(request, request.getResponse(), principal,// request.getResponse()? httpServletRequest.getResponse()
                    "DIGEST",
                    username, password);  
    }
  }

